/**
 *
 */
package br.com.puertorico.model.querybuilder.utils;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;

import br.com.puertorico.model.querybuilder.wrappers.ArgumentWrapper;

/**
 * @author daniel
 * @version 1.0.0
 */
public class ArgumentTree {

	static int ID_GENERATOR;

	private Map<String, ArgumentWrapper> arguments;

	private TreeNode root;

	private Set<TreeNode> nodes;

	/**
	 * @param requiredArguments
	 * @param expressionHint
	 */
	public ArgumentTree(List<ArgumentWrapper> requiredArguments, String expressionHint) {
		arguments = new HashMap<String, ArgumentWrapper>();
		for (ArgumentWrapper arg : requiredArguments) {
			arguments.put(arg.getId(), arg);
		}
		if (!arguments.isEmpty()) {
			nodes = new LinkedHashSet<TreeNode>();
			buildTree(PostfixExpressionBuilder.infixToPostfix(expressionHint));
			// remover os nos que nao farao parte de consulta
			for (TreeNode node : nodes) {
				if (node.operand && !arguments.containsKey(node.label)) {
					removeOperand(node.label);
				}
			}
		}
	}

	private void buildTree(String postfix) {
		StringTokenizer st = new StringTokenizer(postfix);
		Stack<String> tokenStack = new Stack<String>();
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			tokenStack.push(token);
		}
		// vamos percorrer a pilha
		while (!tokenStack.isEmpty()) {
			String token = tokenStack.pop();
			TreeNode newNode = null;
			if (root == null) {
				root = new TreeNode(token);
				nodes.add(root);
			} else {
				newNode = new TreeNode(token);
				root.insertChild(newNode);
				nodes.add(newNode);
			}
		}
	}

	/**
	 * @param label
	 */
	private void removeOperand(String label) {
		if (root != null && root.label.equals(label)) {
			root = null;
		} else {
			removeOperand(label, root);
			// verificar se o root perdeu filho
			if (root.left == null && root.right != null) {
				root = root.right;
			}
			// verificar se o root perdeu filho
			if (root.right == null && root.left != null) {
				root = root.left;
			}
		}
	}

	/**
	 * @param label
	 * @param subTree
	 */
	private void removeOperand(String label, TreeNode subTree) {
		// achamos o no a remover no filho esquerdo
		if (subTree.left != null && subTree.left.label.equals(label)) {
			subTree.left = null;
			if (subTree.parent != null) {
				// se a subtree eh o filho a esquerda
				if (subTree.parent.left != null
						&& subTree.parent.left.equals(subTree)) {
					subTree.parent.left = subTree.right;
					subTree.right.parent = subTree.parent;
				} else {
					// se a subtree eh o filho a direita
					subTree.parent.right = subTree.right;
					subTree.right.parent = subTree.parent;
				}
			}
		}
		// achamos o no a remover no filho direito
		if (subTree.right != null && subTree.right.label.equals(label)) {
			subTree.right = null;
			if (subTree.parent != null) {
				// se a subtree eh o filho a esquerda
				if (subTree.parent.left != null
						&& subTree.parent.left.equals(subTree)) {
					subTree.parent.left = subTree.left;
					subTree.left.parent = subTree.parent;
				} else {
					// se a subtree eh o filho a direita
					subTree.parent.right = subTree.left;
					subTree.left.parent = subTree.parent;
				}
			}
		}
		if (subTree.left != null && !subTree.left.operand) {
			removeOperand(label, subTree.left);
		}
		if (subTree.right != null && !subTree.right.operand) {
			removeOperand(label, subTree.right);
		}
	}

	/**
	 * @return a expressao
	 */
	public String getExpression() {
		if (root == null) {
			return "";
		}
		getExpression(root);
		TreeNode nohLeft = root.left;
		TreeNode nohRight = root.right;
		StringBuilder where = new StringBuilder();
		if (nohLeft != null && nohRight != null) {
			extractValue(where, nohLeft);
			where.append(" ").append(root.label).append(" ");
			extractValue(where, nohRight);
		} else {
			extractValue(where, root);
		}
		return where.toString();
	}

	/**
	 * @param subTree
	 */
	private void getExpression(TreeNode subTree) {
		if (subTree != null) {
			if (subTree.left != null && !subTree.left.operand) {
				getExpression(subTree.left);
			}
			if (subTree.right != null && !subTree.right.operand) {
				getExpression(subTree.right);
			}
			if (subTree.left != null && subTree.left.operand
					&& subTree.right != null && subTree.right.operand) {
				StringBuilder where = new StringBuilder();
				where.append("(");
				extractValue(where, subTree.left);
				where.append(" ").append(subTree.label).append(" ");
				extractValue(where, subTree.right);
				where.append(")");
				subTree.value = where.toString();
				subTree.operand = true;
			}
		}
	}

	/**
	 * @param where
	 * @param node
	 */
	private void extractValue(StringBuilder where, TreeNode node) {
		if (!node.operand && node.value == null) {
			return;
		}
		if (arguments.containsKey(node.label)) {
			where.append(arguments.get(node.label).getWhereClause());
		} else {
			where.append(node.value);
		}
	}

	/**
	 * @author daniel
	 * @version 1.0.0
	 */
	private class TreeNode {

		Integer id;

		String label;

		String value;

		boolean operand;

		boolean backtracked;

		TreeNode parent;

		TreeNode left;

		TreeNode right;

		/**
		 * @param label
		 */
		TreeNode(String label) {
			this.label = label;
			id = ID_GENERATOR++;
			switch (PostfixExpressionBuilder.getTokenType(label)) {
			case OPERAND:
				operand = true;
				break;
			default:
				operand = false;
				break;
			}
		}

		/**
		 * @param child
		 */
		void insertChild(TreeNode child) {
			insertChild(child, this);
		}

		/**
		 * @param child
		 * @param subTree
		 * @return
		 */
		boolean insertChild(TreeNode child, TreeNode subTree) {
			if (subTree.right == null) {
				child.parent = subTree;
				subTree.right = child;
				return true;
			}
			if (!subTree.right.operand && !subTree.right.backtracked) {
				return insertChild(child, subTree.right);
			}
			if (subTree.left == null) {
				child.parent = subTree;
				subTree.left = child;
				return true;
			}
			if (!subTree.left.operand && !subTree.left.backtracked) {
				return insertChild(child, subTree.left);
			}
			subTree.backtracked = true;
			if (subTree.parent == null) {
				return true;
			}
			return insertChild(child, subTree.parent);
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
			return id.hashCode();
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj) {
			TreeNode cand = (TreeNode) obj;
			return cand.id.equals(id);
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
			return id + "@" + label;
		}
	}

}
